package com.feedgraph;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import org.json.JSONException;
import org.json.JSONObject;

import com.feedgraph.FeedManager.Feed;
import com.feedgraph.FeedManager.FeedItem;
import com.feedgraph.FeedManager.FeedListener;
import com.google.android.gcm.GCMRegistrar;

import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.util.Log;

public class FeedService extends Service {

	protected FeedManager mFeedManager;
	protected Set<String> mWatchers = new HashSet<String>();
	protected Handler mHandler;
	protected Feed mSubscriptionFeed;
	protected List<Subscription> mSubscriptions = new ArrayList<Subscription>();

	@Override
	public void onCreate () {
		mFeedManager = new FeedManager (this);
		
		// associates the handler with whichever thread created the FeedService
		mHandler = new Handler();
	}
	
	public void registerGCM(String senderId) {

		GCMRegistrar.checkDevice(this);
		GCMRegistrar.checkManifest(this);
		final String regId = GCMRegistrar.getRegistrationId(this);
		if (regId.equals("")) {
		  GCMRegistrar.register(this, senderId);
		  Log.v(getClass().getSimpleName (), "GCM service registered");
		} else {
		  Log.v(getClass().getSimpleName (), "Already registered");
		}
	}

	@Override
	public int onStartCommand (Intent intent, int flags, int startID) {
		Log.i (getClass ().getSimpleName (), "onStartCommand");

		// We want this service to continue running until it is explicitly
		// stopped, so return sticky.
		return START_STICKY;
	}

	@Override
	public IBinder onBind (Intent arg0) {
		return mBinder;
	}

	public FeedManager getManager () {
		return mFeedManager;
	}

	// This is the object that receives interactions from clients. See
	// RemoteService for a more complete example.
	private final IBinder mBinder = new LocalBinder ();

	/**
	 * Class for clients to access. Because we know this service always runs in
	 * the same process as its clients, we don't need to deal with IPC.
	 */
	public class LocalBinder extends Binder {
		public FeedService getService () {
			return FeedService.this;
		}
	}
	
	public void fetch (String graph, String node, String rel) {
		FeedTask task = new FeedTask (graph, node, rel);

		task.executeOnExecutor (AsyncTask.THREAD_POOL_EXECUTOR);
	}

	/** register this device for GCM messages, which will arrive whenever the node
	 * is modified in any way
	 * @param graph
	 * @param node
	 * @throws IOException
	 */
	public void watch (String graph, String node) throws IOException {
		
		SubscriptionTask task = new SubscriptionTask(graph, node);
		task.execute();
		
		FeedTask task2 = new FeedTask(graph, node, "*");
		task2.execute();
	}
	
	public int deleteFeedItem(String uuid) {
		return getContentResolver().delete (FeedManager.CONTENT_URI, "_id = ?", new String[] {uuid});
	}
	
	public void addListener(String node, String rel, FeedListener listener) {
		
		mSubscriptions.add (new Subscription(node, rel, listener));
	}
	
	private class SubscriptionTask extends AsyncTask<Void, Void, Void> {
		
		String mGraph, mNode;
		
		protected SubscriptionTask(String graph, String node) {
			mGraph = graph;
			mNode = node;
		}

		@Override
		protected Void doInBackground (Void... params) {
			JSONObject json = new JSONObject();
			
			try {
				json.put ("AndroidRegId", GCMRegistrar.getRegistrationId (FeedService.this));
			}
			catch (JSONException e) {
				throw new Error(e);
			}
			
			Feed subs = FeedManager.makeFeed (mGraph, "subscriptions", mNode);
			try {
				subs.postItem (json, null);
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return null;
		}
	}
	
	private class FeedTask extends AsyncTask <Void, Void, Void> {
		Feed mFeed;
		BlockingQueue<FeedItem> mNewItems = new ArrayBlockingQueue<FeedItem> (10);
		
		Runnable mRunnable = new Runnable () {

			@Override
			public void run () {
				Log.d("FeedTaskRunnable", "spooling to db " + mNewItems.size () + " items");
				for (FeedItem item : mNewItems ) {
					storeFeedItem(item);
				}
				mNewItems.clear ();
			}
			
		};
		
		public FeedTask(String graph, String node, String rel) {
			super ();
			mFeed = FeedManager.makeFeed (graph, node, rel);
		}

		@Override
		protected void onPostExecute (Void result) {
			Log.d (getClass ().getSimpleName (), "onPostExecute");
			mFeed.clear ();
		}
		
		protected void fetchNew() {
			try {
				mFeed.fetchNew ();
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace ();
			}
			catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace ();
			}
		}
		
		private void queueNewItems() {
			for (FeedItem item : mFeed) {
				try {
					mNewItems.put (item);
				}
				catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

		@Override
		protected Void doInBackground (Void... params) {
			
				fetchNew();
				queueNewItems();
				mHandler.post (mRunnable);
				
				Log.d (getClass ().getSimpleName (), "fetched " + mFeed.size () + " from " + mFeed.mUri);
				
				return null;
		}

	}

	public static Intent getIntent (Context context) {
		return new Intent (context, FeedService.class);
	}  
 
  public void handleItemMessage(FeedItem item) {
  	Log.d(getClass().getSimpleName (), item.getUUID ());
  	Log.d(getClass().getSimpleName (), item.mNode + ", " + item.mRelation);
  	
  	storeFeedItem(item);
		broadcastToListeners(item);
  }
  
  private void storeFeedItem(FeedItem item) {
  	getContentResolver().insert (FeedManager.CONTENT_URI, item.toContentValues ());
  }
  
  private void broadcastToListeners(FeedItem item) {
  	for (Subscription s : mSubscriptions ) {
  		if (s.match (item)) s.mListener.onFeedItem (item);
  	}
  }
  
  protected static class Subscription {
  	
  	protected PatternMatcher mNodeMatcher;
  	protected PatternMatcher mRelationMatcher;
  	protected FeedListener mListener;
  	
  	public Subscription (String node, String rel, FeedListener listener) {
  		mNodeMatcher = new PatternMatcher(node, PatternMatcher.PATTERN_SIMPLE_GLOB);
  		mRelationMatcher = new PatternMatcher(rel, PatternMatcher.PATTERN_SIMPLE_GLOB);
  		mListener = listener;
  	}
  	
  	public boolean match (FeedItem item) {
  		return mNodeMatcher.match (item.mNode) && mRelationMatcher.match (item.mRelation);
  	}
  }
 
}
